﻿using Foundation.Core;
using Microsoft.Extensions.Caching.Memory;
using Nito.AsyncEx;

namespace Foundation.Core
{
    public class SelectCollectionService : ISelectCollectionService
    {
        private readonly SelectCollectionStore _store;
        private readonly IServiceProvider _serviceProvider;
        private readonly SelectCollectionParameterTypeValueProviderRegistrar _valueProviderRegistrar;

        public SelectCollectionService(SelectCollectionStore store, SelectCollectionParameterTypeValueProviderRegistrar valueProviderRegistrar, IServiceProvider serviceProvider)
        {
            this._store = store;
            this._valueProviderRegistrar = valueProviderRegistrar;
            this._serviceProvider = serviceProvider;
        }

        public virtual async Task<SelectCollection> GetCollctionAsync(string key, SelectCategory category, IDictionary<string, object> arguments, CancellationToken cancellationToken)
        {
            if (category == SelectCategory.Custom)
            {
                var o = _store.GetOptions(key, category);
                var methodOptions = o?.MethodOptions;
                if (methodOptions == null) return null;

                if (!methodOptions.IsNoReferenceMethod) //有参查询
                {
                    SelectCollection result = null;
                    var convert = SelectItemLoadMethodConverterFactory.GetConverter(_serviceProvider, methodOptions);

                    arguments ??= new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

                    await LoadArgumentsAsync(key, category, methodOptions, arguments, cancellationToken);

                    if (!methodOptions.ParametersAllOptional && arguments.IsNullOrEmpty())
                    {
                        throw new BusinessException("SelectColletionService_NotArguments", $"select item key:[{key}],category:{category}是有参查询选项，请提供参数！");
                    }

                    if (methodOptions.Cacheable)
                    {
                        var cacheKey = convert.BuildCacheKey(arguments);

                        cacheKey = $"{key}_{cacheKey}";

                        result = await _store.MemoryCache.GetOrCreateAsync(cacheKey, async entry =>
                        {
                            var creator = GetCreator(o);

                            if (creator != null)
                            {
                                var collection = await creator.CreateAsync(key, category, arguments, cancellationToken);
                                entry.SetAbsoluteExpiration(o.CacheTime.GetValueOrDefault(TimeSpan.Zero));

                                return collection;
                            }

                            return null;
                        });
                    }
                    else
                    {
                        var creator = GetCreator(o);

                        if (creator != null)
                        {
                            result = await creator.CreateAsync(key, category, arguments, cancellationToken);
                            return result;
                        }
                    }

                    return result;
                }
            }

            if (!_store.TryGetCollection(key, category, out var collection) && _store.TryGetOptions(key, category, out var options))
            {
                using (await options.Slim.LockAsync(cancellationToken))
                {
                    if (!_store.TryGetCollection(key, category, out collection))
                    {
                        var creator = GetCreator(options);

                        if (creator != null)
                        {
                            collection = await creator.CreateAsync(key, category, null, cancellationToken);

                            _store.SetCollection(key, category, collection);
                        }
                    }
                }
            }

            return collection;
        }

        protected virtual async Task LoadArgumentsAsync(string key, SelectCategory category, SelectItemLoadMethodOptions methodOptions, IDictionary<string, object> arguments, CancellationToken cancellationToken)
        {
            if (!methodOptions.IsNoReferenceMethod && !methodOptions.HasScanParameterValueProvider)
            {
                lock (methodOptions)
                {
                    if (!methodOptions.HasScanParameterValueProvider)
                    {
                        foreach (var item in methodOptions.Parameters)
                        {
                            if (item.IsCancellationToken) continue;
                            if (item.ValueProviderType is null)
                            {
                                item.ValueProviderType = _valueProviderRegistrar.FindValueProvider(item.ParameterType);
                            }
                        }

                        methodOptions.HasScanParameterValueProvider = true;
                    }
                }
            }

            foreach (var p in methodOptions.Parameters)
            {
                if (p.IsCancellationToken || p.ValueProviderType is null) continue;

                var provider = _serviceProvider.GetService(p.ValueProviderType) as ISelectParameterValueProvider;

                if (provider == null)
                {
                    throw new BusinessException("SelectColletionService_ParameterValuePorviderNotFound", $"select item key:[{key}],category:{category},未能实例化方法参数 “{p.PrameterName}”的 ValueProvider。");
                }

                ISelectParameterValue result = null;
                if (provider is ISelectParameterAsyncValueProvider asyncValueProvider)
                {
                    result = await asyncValueProvider.GetValueAsync(arguments, p, cancellationToken);
                }
                else
                {
                    result = provider.GetValue(arguments, p);
                }

                if (result == null || !result.Success)
                {
                    throw new BusinessException("SelectColletionService_ParameterValuePorviderParserFailed", $"select item key:[{key}],category:{category},未能解析“{p.PrameterName}”参数值。");
                }

                arguments[$"$.parameterValue.{p.PrameterName}"] = result;
            }
        }

        protected virtual ISelectCollectionCreator GetCreator(SelectItemOptions options)
        {
            var creator = options.Creator;

            if (creator != null) return creator;

            if (options.Factory != null)
            {
                var factory = _serviceProvider.GetService(options.Factory) as ISelectCollectionCreatorFactory;

                if (factory != null)
                {
                    creator = factory.Create(options);
                    if (creator != null) return creator;
                }

                throw new AppException($"{options.Category},{options.Key}配置工厂类 {options.Factory} 未找到或未注册依赖服务");
            }

            if (options.CreatorType != null)
            {
                creator = _serviceProvider.GetService(options.CreatorType) as ISelectCollectionCreator;

                if (creator == null)
                {
                    throw new AppException($"{options.Category},{options.Key} 配置 CreatorType:{options.CreatorType} 未找到或未注册依赖服务");
                }
            }

            if (creator == null && (options.Category == SelectCategory.Enum || options.Category == SelectCategory.Constant))
            {
                var registar = _serviceProvider.GetService(typeof(SelectFilterRegistrar)) as SelectFilterRegistrar;
                creator = new TypeSelectCollectionCreator(options, registar);

                options.AddCreator(creator);
            }

            return creator;
        }
    }
}
